﻿using System;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Helper structure that can be used to initialize or transfer the values of a <see cref="ToneElement"/>.
    /// </summary>
    public struct Tone : IComparable<Tone>, IComparable<ToneElement>
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Tone"/> instance.
        /// </summary>
        /// <param name="note">
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The note.
        /// </para>
        /// </param>
        /// <param name="octave">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The octave.
        /// </para>
        /// </param>
        public Tone(byte note, int octave)
            : this()
        {
            Note = note;
            Octave = octave;
        }

        /// <summary>
        /// Creates a new <see cref="Tone"/> instance.
        /// </summary>
        /// <param name="note">
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The note.
        /// </para>
        /// </param>
        /// <param name="octave">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The octave.
        /// </para>
        /// </param>
        /// <param name="accidental">
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The accidental.
        /// </para>
        /// </param>
        public Tone(byte note, int octave, double accidental)
            : this()
        {
            Note = note;
            Octave = octave;
            Accidental = accidental;
        }

        /// <summary>
        /// Creates a new <see cref="Tone"/> instance.
        /// </summary>
        /// <param name="source">
        /// <para>
        /// Type: <see cref="Tone"/>
        /// </para>
        /// <para>
        /// An other tone structure that will initialize the new instance.
        /// </para>
        /// </param>
        public Tone(Tone source)
            : this()
        {
            this.Note = source.Note;
            this.Accidental = source.Accidental;
            this.Octave = source.Octave;
        }

        #endregion

        #region IComparable<Tone> Members

        /// <inheritdoc/>
        public int CompareTo(Tone other)
        {
            if (other.Octave > Octave)
                return -1;
            else if (other.Octave < Octave)
                return 1;
            else
            {
                if (other.Note + other.Accidental > Note + Accidental)
                    return -1;
                else if (other.Note + other.Accidental < Note + Accidental)
                    return 1;
                else
                    return 0;
            }
        }

        #region IComparable<ToneElement> Members

        /// <inheritdoc/>
        public int CompareTo(ToneElement other)
        {
            if (other.Octave > Octave)
                return -1;
            else if (other.Octave < Octave)
                return 1;
            else
            {
                if (other.Note + other.Accidental > Note + Accidental)
                    return -1;
                else if (other.Note + other.Accidental < Note + Accidental)
                    return 1;
                else
                    return 0;
            }
        }

        #endregion

        #endregion

        #region Public Properties

        /// <summary>
        /// Gives the value of the accidental that modifies this tone.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Double"/>
        /// </para>
        /// <para>
        /// The accidental lowers or raises a note by up to one tone.
        /// </para>
        /// <para>
        /// In this representation the accidental is a real number ranging from -1.0 to 1.0
        /// where 0.0 stands for no change, 0.5 would change the note by one semitone and 0.25 by one quarter tone.
        /// </para>
        /// </value>
        public double Accidental
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the octave of this tone.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Int32"/>
        /// </para>
        /// <para>
        /// An octave is the interval between one musical pitch and another with half or double its frequency.
        /// </para>
        /// <para>
        /// 1 stands for a one-lined octave (the one which starts with middle c ~ 261.63Hz).<br/>
        /// Smaller values represents lower octaves, greater values represents higher octaves,
        /// e.g. 3 stands for three-lined octave (c ~ 1046.50Hz). 
        /// </para>
        /// </value>
        public int Octave
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the name of the note (in the chromatic system).
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Byte"/>
        /// </para>
        /// <para>
        /// Two notes with fundamental frequencies in a ratio of any power of two 
        /// (e.g. half, twice, or four times) are perceived as very similar. 
        /// Because of that, all notes with these kinds of relations can be grouped 
        /// under the same pitch class.
        /// </para>
        /// <para>
        /// The pitch class in this representation is expressed as an integer ranging from 1 to 7,
        /// thus giving a scale of 7 tones (C, D, E, F, G, A, B [H]).
        /// </para>
        /// </value>
        /// <seealso cref="Octave"/>
        /// <seealso cref="Accidental"/>
        public byte Note
        {
            get;
            set;
        }

        #endregion
    }
}
